package com.fitso.model.dao.hibernate.type.nutrient;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;

import com.fitso.model.bean.measure.Measure;
import com.fitso.model.bean.measure.mass.Gram;
import com.fitso.model.bean.measure.mass.Mass;
import com.fitso.model.bean.measure.volume.MilliLiter;
import com.fitso.model.bean.measure.volume.Volume;
import com.fitso.model.bean.nutrition.Serving;
import com.fitso.model.util.common.StringUtils;

/**
 * Conversion utility to/from the database/java type of {@link Serving} types
 * 
 * @author timothystorm
 * 
 */
public class ServingHibernateType implements CompositeUserType {

	private enum MeasureType {
		MASS, VOLUME;
	}

	public Class<Measure> returnedClass() {
		return Measure.class;
	}

	public Object assemble(Serializable cached, SessionImplementor session, Object owner) throws HibernateException {
		return null;
	}

	public Object deepCopy(Object value) throws HibernateException {
		if (value != null) {
			if (ClassUtils.isAssignable(value.getClass(), Mass.class)) {
				return new Gram((Mass) value);
			} else if (ClassUtils.isAssignable(value.getClass(), Volume.class)) {
				return new MilliLiter((Volume) value);
			}
		}
		return null;
	}

	public Serializable disassemble(Object value, SessionImplementor session) throws HibernateException {
		return null;
	}

	public boolean equals(Object o1, Object o2) throws HibernateException {
		if (o1 != null) {
			return ((Measure) o1).equals(o2);
		} else if (o2 != null) {
			return ((Measure) o2).equals(o1);
		} else if (o1 == null && o2 == null) {
			return true;
		}
		return false;
	}

	private static final String[] PROP_NAMES = new String[] { "unit_value", "unit_type" };

	public String[] getPropertyNames() {
		return PROP_NAMES;
	}

	private static final Type[] PROP_TYPES = new Type[] { Hibernate.BIG_DECIMAL, Hibernate.STRING };

	public Type[] getPropertyTypes() {
		return PROP_TYPES;
	}

	public Object getPropertyValue(Object component, int property) throws HibernateException {
		if (component == null) {
			return null;
		}

		Measure measure = (Measure) component;

		if (property == 0) {
			return measure.getValue();
		}
		return null;
	}

	public int hashCode(Object x) throws HibernateException {
		return new HashCodeBuilder().append(x).toHashCode();
	}

	public boolean isMutable() {
		return true;
	}

	public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
			throws HibernateException, SQLException {
		MeasureType measureType = MeasureType.valueOf(StringUtils.upperCase(rs.getString(names[0])));
		BigDecimal unitValue = rs.getBigDecimal(names[1]);

		if (MeasureType.MASS == measureType) {
			return new Gram(unitValue);
		} else if (MeasureType.VOLUME == measureType) {
			return new MilliLiter(unitValue);
		}
		return null;
	}

	public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
			throws HibernateException, SQLException {
		if (value != null) {
			if (ClassUtils.isAssignable(value.getClass(), Mass.class)) {
				Hibernate.STRING.nullSafeSet(st, MeasureType.MASS.toString(), index);
				Hibernate.BIG_DECIMAL.nullSafeSet(st, new Gram((Mass) value).getValue(), index + 1);
			} else if (ClassUtils.isAssignable(value.getClass(), Volume.class)) {
				Hibernate.STRING.nullSafeSet(st, MeasureType.VOLUME.toString(), index);
				Hibernate.BIG_DECIMAL.nullSafeSet(st, new MilliLiter((Volume) value).getValue(), index + 1);
			}
		}
	}

	public Object replace(Object original, Object target, SessionImplementor session, Object owner)
			throws HibernateException {
		return null;
	}

	public void setPropertyValue(Object component, int property, Object value) throws HibernateException {}
}
